Omanda Pythoni asyncio Future'id. Uuri madalatasemelisi asünkroonseid kontseptsioone, näiteid ja edasijõudnute tehnikaid tugevate, suure jõudlusega rakenduste loomiseks.
Asyncio Future'id avatud: Süvenemine madalatasemelisse asünkroonsesse programmeerimisse Pythonis
Kaasaegse Pythoni arenduse maailmas on async/await
süntaksist saanud nurgakivi suure jõudlusega, I/O-mahukate rakenduste loomisel. See pakub puhta ja elegantse viisi paralleelse koodi kirjutamiseks, mis näib peaaegu järjestikune. Kuid selle kõrgetasemelise süntaktilise suhkru all peitub võimas ja fundamentaalne mehhanism: Asyncio Future. Kuigi te ei pruugi toorete Future'idega iga päev kokku puutuda, on nende mõistmine võti asünkroonse programmeerimise tõeliseks omandamiseks Pythonis. See on nagu auto mootori töö õppimine; te ei pea seda teadma, et sõita, kuid see on oluline, kui soovite olla meistermehaanik.
See põhjalik juhend avab asyncio
eesriide. Uurime, mis Future'id on, kuidas nad erinevad korutiinidest ja ülesannetest ning miks see madalatasemeline primitiiv on alus, millele Pythoni asünkroonsed võimalused on ehitatud. Olenemata sellest, kas te silute keerulist võidujooksu olukorda, integreerite vanemate tagasihelistamisel põhinevate teekidega või soovite lihtsalt asünkroonse programmeerimise sügavamat mõistmist, on see artikkel teile.
Mis täpselt on Asyncio Future?
Oma olemuselt on asyncio.Future
objekt, mis esindab asünkroonse operatsiooni lõpptulemust. Mõelge sellele kui kohatäitele, lubadusele või kviitungile väärtusele, mis pole veel saadaval. Kui käivitate operatsiooni, mis võtab aega (nt võrgupäring või andmebaasipäring), saate Future objekti kohe tagasi. Teie programm saab jätkata muude tööde tegemist ja kui operatsioon lõpuks lõpeb, paigutatakse tulemus (või viga) sellesse Future objekti.
Abistav reaalmaailma analoogia on kohvi tellimine hõivatud kohvikus. Te esitate tellimuse ja maksate ning barista annab teile kviitungi tellimuse numbriga. Teie kohvi pole veel, kuid teil on kviitung – kohvi lubadus. Nüüd saate minna lauda otsima või telefoni kontrollima, selle asemel, et leti ääres tühjalt seista. Kui teie kohv on valmis, kutsutakse teie number ja saate oma kviitungi lõpptulemuse vastu 'lunastada'. Kviitung on Future.
Future'i põhiomadused on järgmised:
- Madalatasemeline: Future'id on primitiivsem ehitusplokk võrreldes ülesannetega. Nad ei tea olemuslikult, kuidas koodi käivitada; nad on lihtsalt konteinerid tulemuse jaoks, mis määratakse hiljem.
- Ootatav: Future'i kõige olulisem omadus on see, et see on ootatav objekt. See tähendab, et saate sellel kasutada võtmesõna
await
, mis peatab teie korutiini täitmise, kuni Future'il on tulemus. - Olekuga: Future eksisteerib oma elutsükli jooksul ühes mitmest erinevast olekust: Ootel (Pending), Tühistatud (Cancelled) või Lõpetatud (Finished).
Future'id vs. Korutiinid vs. Ülesanded: Segaduse selgitamine
Üks suurimaid takistusi arendajatele, kes on asyncio
uustulnukad, on nende kolme põhikontseptsiooni vahelise suhte mõistmine. Need on sügavalt omavahel seotud, kuid teenivad erinevaid eesmärke.
1. Korutiinid
Korutiin on lihtsalt funktsioon, mis on defineeritud async def
-iga. Kui te korutiini funktsiooni välja kutsute, ei täida see oma koodi. Selle asemel tagastab see korutiini objekti. See objekt on arvutuse plaan, kuid midagi ei juhtu enne, kui seda juhib sündmuste tsükkel.
Näide:
async def fetch_data(url): ...
Kutsuge fetch_data("http://example.com")
ja saate korutiini objekti. See on inertne, kuni te seda await
-ite või ajastate selle ülesandena.
2. Ülesanded (Tasks)
asyncio.Task
on see, mida te kasutate korutiini sündmuste tsüklis samaaegselt käivitamiseks. Te loote ülesande, kasutades asyncio.create_task(my_coroutine())
. Ülesanne mässib teie korutiini ja ajastab selle koheselt "taustal" käivituma niipea, kui sündmuste tsükkel selleks võimaluse saab. Oluline on siinkohal mõista, et ülesanne on Future'i alamklass. See on spetsialiseeritud Future, mis teab, kuidas korutiini juhtida.
Kui mähitud korutiin lõpetab ja tagastab väärtuse, määratakse ülesande (mis, tuletame meelde, on Future) tulemus automaatselt. Kui korutiin tekitab erandi, määratakse ülesande erand.
3. Future'id
Tavaline asyncio.Future
on veelgi fundamentaalsem. Erinevalt ülesandest ei ole see seotud ühegi konkreetse korutiiniga. See on lihtsalt tühi kohatäide. Midagi muud – teine osa teie koodist, teek või sündmuste tsükkel ise – vastutab selle tulemuse või erandi hilisema selgesõnalise seadmise eest. Ülesanded haldavad seda protsessi teie eest automaatselt, kuid toore Future'iga on haldamine käsitsi.
Siin on kokkuvõtlik tabel, et vahet selgitada:
Kontseptsioon | Mis see on | Kuidas see luuakse | Peamine kasutusjuhtum |
---|---|---|---|
Korutiin | Funktsioon, mis on defineeritud async def -iga; generaatoripõhine arvutusplaan. |
async def my_func(): ... |
Asünkroonse loogika defineerimine. |
Ülesanne (Task) | Future'i alamklass, mis mässib ja käivitab korutiini sündmuste tsüklis. | asyncio.create_task(my_func()) |
Korutiinide samaaegne käivitamine ("käivita ja unusta"). |
Future | Madalatasemeline ootatav objekt, mis esindab lõpptulemust. | loop.create_future() |
Liidestamine tagasihelistamisel põhineva koodiga; kohandatud sünkroniseerimine. |
Lühidalt: Te kirjutate korutiine. Te käivitate neid samaaegselt, kasutades ülesandeid. Nii ülesanded kui ka alusfunktsionaalsed I/O operatsioonid kasutavad Future'e kui põhilist mehhanismi lõpetamise signaliseerimiseks.
Future'i elutsükkel
Future läbib lihtsa, kuid olulise olekute hulga. Selle elutsükli mõistmine on nende tõhusaks kasutamiseks võtmetähtsusega.
Olek 1: Ootel (Pending)
Kui Future esmakordselt luuakse, on see ootel olekus. Sellel pole tulemust ega erandit. See ootab, et keegi selle lõpetaks.
import asyncio
async def main():
# Get the current event loop
loop = asyncio.get_running_loop()
# Create a new Future
my_future = loop.create_future()
print(f"Is the future done? {my_future.done()}") # Väljund: False
# To run the main coroutine
asyncio.run(main())
Olek 2: Lõpetamine (Tulemuse või Erandi määramine)
Ootel olev Future saab lõpetada ühel kahest viisist. Seda teeb tavaliselt tulemuse "tootja".
1. Eduka tulemuse määramine meetodiga set_result()
:
Kui asünkroonne operatsioon lõpetatakse edukalt, lisatakse selle tulemus Future'ile selle meetodi abil. See viib Future'i lõpetatud olekusse.
2. Erandi määramine meetodiga set_exception()
:
Kui operatsioon ebaõnnestub, lisatakse Future'ile erandi objekt. See viib Future'i samuti lõpetatud olekusse. Kui teine korutiin `await`ib seda Future'it, tõstetakse lisatud erand.
Olek 3: Lõpetatud (Finished)
Kui tulemus või erand on määratud, loetakse Future lõpetatuks. Selle olek on nüüd lõplik ja seda ei saa muuta. Saate seda kontrollida meetodiga future.done()
. Kõik korutiinid, mis ootasid (await
isid) seda Future'it, ärkavad nüüd üles ja jätkavad oma täitmist.
(Valikuline) Olek 4: Tühistatud (Cancelled)
Ootel olev Future saab tühistada ka meetodit future.cancel()
välja kutsudes. See on taotlus operatsioonist loobumiseks. Kui tühistamine on edukas, läheb Future tühistatud olekusse. Oodates tõstab tühistatud Future CancelledError
erandi.
Future'idega töötamine: Praktilised näited
Teooria on oluline, kuid kood muudab selle reaalseks. Vaatame, kuidas saate kasutada tooreid Future'e konkreetsete probleemide lahendamiseks.
Näide 1: Käsitsi tootja/tarbija stsenaarium
See on klassikaline näide, mis demonstreerib põhilist suhtlusmustrit. Meil on üks korutiin (`tarbija`), mis ootab Future'it, ja teine (`tootja`), mis teeb tööd ja seejärel määrab tulemuse sellele Future'ile.
import asyncio
import time
async def producer(future):
print("Tootja: Alustan rasket arvutust...")
await asyncio.sleep(2) # Simuleeri I/O- või CPU-intensiivset tööd
result = 42
print(f"Tootja: Arvutus lõpetatud. Määramise tulemus: {result}")
future.set_result(result)
async def consumer(future):
print("Tarbija: Ootan tulemust...")
# Võtmesõna 'await' peatab tarbija siin, kuni Future on valmis
result = await future
print(f"Tarbija: Sain tulemuse! See on {result}")
async def main():
loop = asyncio.get_running_loop()
my_future = loop.create_future()
# Ajasta tootja taustal käivituma
# See töötab my_future lõpetamise kallal
asyncio.create_task(producer(my_future))
# Tarbija ootab tootja lõpetamist Future'i kaudu
await consumer(my_future)
asyncio.run(main())
# Eeldatav väljund:
# Tarbija: Ootan tulemust...
# Tootja: Alustan rasket arvutust...
# (2-sekundiline paus)
# Tootja: Arvutus lõpetatud. Määramise tulemus: 42
# Tarbija: Sain tulemuse! See on 42
Selles näites toimib Future sünkroniseerimispunktina. `Tarbija` ei tea ega hooli sellest, kes tulemuse annab; ta hoolib ainult Future'ist endast. See lahutab tootja ja tarbija, mis on samaaegsetes süsteemides väga võimas muster.
Näide 2: Sildamine tagasihelistamisel põhinevate API-dega
See on üks võimsamaid ja levinumaid toorete Future'ide kasutusjuhtumeid. Paljud vanemad teegid (või teegid, mis vajavad liidestamist C/C++-ga) ei ole `async/await`-i põliskomponendid. Selle asemel kasutavad nad tagasihelistamisel põhinevat stiili, kus te annate funktsiooni, mis täidetakse pärast lõpetamist.
Future'id pakuvad täiuslikku silda nende API-de moderniseerimiseks. Saame luua mähisefunktsiooni, mis tagastab oodatava Future'i.
Kujutame ette, et meil on hüpoteetiline pärandfunktsioon legacy_fetch(url, callback)
, mis toob URL-i ja kutsub lõpetamisel välja `callback(data)`.
import asyncio
from threading import Timer
# --- See on meie hüpoteetiline pärandteek ---
def legacy_fetch(url, callback):
# See funktsioon ei ole asünkroonne ja kasutab tagasihelistamisi.
# Me simuleerime võrguviivitust threading mooduli taimeriga.
print(f"[Pärand] Tõmban {url}... (See on blokeeriv stiilis kõne)")
def on_done():
data = f"Mingi andmed saidilt {url}"
callback(data)
# Simuleeri 2-sekundilist võrgukõnet
Timer(2, on_done).start()
# -------------------------------------------
async def modern_fetch(url):
"""Meie oodatav mähis ümber pärandfunktsiooni."""
loop = asyncio.get_running_loop()
future = loop.create_future()
def on_fetch_complete(data):
# See tagasihelistamine täidetakse teises lõimes.
# Et Future'i tulemust ohutult peamise sündmuste tsükli külge seadistada,
# kasutame loop.call_soon_threadsafe'i.
loop.call_soon_threadsafe(future.set_result, data)
# Kutsuge pärandfunktsioon välja meie spetsiaalse tagasihelistamisega
legacy_fetch(url, on_fetch_complete)
# Ootame Future'it, mis lõpetatakse meie tagasihelistamisega
return await future
async def main():
print("Alustan kaasaegset andmete tõmbamist...")
data = await modern_fetch("http://example.com")
print(f"Kaasaegne andmete tõmbamine lõpetatud. Vastuvõetud: '{data}'")
asyncio.run(main())
See muster on uskumatult kasulik. Funktsioon `modern_fetch` peidab kogu tagasihelistamise keerukuse. Funktsiooni `main` vaatenurgast on see lihtsalt tavaline `async` funktsioon, mida saab oodata. Oleme edukalt "future'iseerinud" pärand-API.
Märkus: loop.call_soon_threadsafe
kasutamine on kriitiline, kui tagasihelistamine täidetakse teises lõimes, nagu on tavaline I/O operatsioonidega teekides, mis ei ole asyncio-ga integreeritud. See tagab, et future.set_result
kutsutakse ohutult välja asyncio sündmuste tsükli kontekstis.
Millal kasutada tooreid Future'e (ja millal mitte)
Kättesaadavate võimsate kõrgetasemeliste abstraktsioonidega on oluline teada, millal haarata madalatasemelise tööriista, näiteks Future'i järele.
Kasutage tooreid Future'e, kui:
- Liidestamine tagasihelistamisel põhineva koodiga: Nagu ülaltoodud näites näidatud, on see peamine kasutusjuhtum. Future'id on ideaalne sild.
- Kohandatud sünkroniseerimisprimitiivide ehitamine: Kui teil on vaja luua oma versioon sündmusest (Event), lukust (Lock) või järjekorrast (Queue) spetsiifiliste käitumistega, on Future'id peamine komponent, millele te ehitate.
- Tulemus on toodetud millegi muu kui korutiiniga: Kui tulemus genereeritakse välise sündmuse allika poolt (nt signaal teisest protsessist, sõnum veebisoketi kliendilt), on Future täiuslik viis selle ootel oleva sündmuse esindamiseks asyncio maailmas.
Vältige tooreid Future'e (kasutage selle asemel ülesandeid), kui:
- Te soovite lihtsalt korutiini samaaegselt käivitada: See on
asyncio.create_task()
ülesanne. See tegeleb korutiini mähkimise, ajastamise ja selle tulemuse või erandi edastamisega ülesandele (mis on Future). Toore Future'i kasutamine siin oleks ratta uuesti leiutamine. - Samaaegsete operatsioonide rühmade haldamine: Mitme korutiini käivitamiseks ja nende lõpetamise ootamiseks on kõrgetasemelised API-d nagu
asyncio.gather()
,asyncio.wait()
jaasyncio.as_completed()
palju ohutumad, loetavamad ja vähem veaohtlikud. Need funktsioonid töötavad otse korutiinide ja ülesannetega.
Edasijõudnud kontseptsioonid ja ohud
Future'id ja sündmuste tsükkel
Future on olemuslikult seotud sündmuste tsükliga, milles see loodi. Väljend `await future` töötab, sest sündmuste tsükkel teab sellest konkreetsest Future'ist. See mõistab, et kui see näeb ootel oleval Future'il `await`-i, peaks see peatama praeguse korutiini täitmise ja otsima muud tööd. Kui Future lõpuks lõpetatakse, teab sündmuste tsükkel, millise peatatud korutiini üles äratada.
Seepärast peate Future'i alati looma, kasutades loop.create_future()
, kus loop
on hetkel töötav sündmuste tsükkel. Katsed luua ja kasutada Future'e erinevate sündmuste tsüklite vahel (või erinevates lõimedes ilma õige sünkroniseerimiseta) viivad vigade ja ettearvamatu käitumiseni.
Mida `await` tegelikult teeb
Kui Pythoni interpretaator kohtab result = await my_future
, teeb see kapoti all mõned sammud:
- See kutsub välja
my_future.__await__()
, mis tagastab iteraatori. - See kontrollib, kas future on juba valmis. Kui jah, saab see tulemuse (või tõstab erandi) ja jätkab ilma peatamiseta.
- Kui future on ootel, ütleb see sündmuste tsüklile: "Peata minu täitmine ja palun ärata mind üles, kui see konkreetne future on lõpetatud."
- Sündmuste tsükkel võtab seejärel üle, käivitades teisi valmisolevaid ülesandeid.
- Kui
my_future.set_result()
võimy_future.set_exception()
on välja kutsutud, märgib sündmuste tsükkel Future'i valminuks ja ajastab peatatud korutiini jätkamiseks tsükli järgmises iteratsioonis.
Levinud lõks: Future'ide segiaajamine ülesannetega
Levinud viga on proovida korutiini täitmist Future'iga käsitsi hallata, kui õige tööriist on ülesanne (Task).
Vale viis (ülemäära keeruline):
# See on verbaalne ja ebavajalik
async def main_wrong():
loop = asyncio.get_running_loop()
future = loop.create_future()
# Eraldi korutiin meie sihtmärgi käivitamiseks ja future'i seadmiseks
async def runner():
try:
result = await some_other_coro()
future.set_result(result)
except Exception as e:
future.set_exception(e)
# Peame selle runner-korutiini käsitsi ajastama
asyncio.create_task(runner())
# Lõpuks saame oma future'i oodata
final_result = await future
Õige viis (kasutades ülesannet):
# Ülesanne teeb kõik ülaltoodu teie eest!
async def main_right():
# Ülesanne on Future, mis juhib korutiini automaatselt
task = asyncio.create_task(some_other_coro())
# Saame ülesannet otse oodata
final_result = await task
Kuna Task
on Future
'i alamklass, on teine näide mitte ainult puhtam, vaid ka funktsionaalselt samaväärne ja tõhusam.
Järeldus: Asyncio alustala
Asyncio Future on Pythoni asünkroonse ökosüsteemi laulmata kangelane. See on madalatasemeline primitiiv, mis teeb kõrgetasemelise async/await
võlu võimalikuks. Kuigi teie igapäevane kodeerimine hõlmab peamiselt korutiinide kirjutamist ja nende ajastamist ülesannetena, annab Future'ide mõistmine teile sügava ülevaate sellest, kuidas kõik omavahel seotud on.
Future'ide omandamisega saate võime:
- Siluda enesekindlalt: Kui näete
CancelledError
-it või korutiini, mis kunagi ei naase, saate aru aluseks oleva Future'i või ülesande olekust. - Integreerida mis tahes koodi: Nüüd on teil võime mähkida mis tahes tagasihelistamisel põhinev API ja muuta see kaasaegses asünkroonses maailmas esmaklassiliseks kodanikuks.
- Ehita keerukaid tööriistu: Future'ide teadmised on esimene samm omaenda täiustatud samaaegsete ja paralleelsete programmeerimiskonstruktide loomiseks.
Seega, järgmine kord, kui kasutate asyncio.create_task()
või await asyncio.gather()
, võtke hetk, et hinnata tagasihoidlikku Future'it, mis töötab väsimatult kulisside taga. See on kindel alus, millele ehitatakse tugevaid, skaleeritavaid ja elegantseid asünkroonseid Pythoni rakendusi.